`timescale 1ns / 1ps
/*
    v1: based on arp_rx. date: 2024/3/26 08:38:32 done date:2024/3/26 10:25:44
*/
module udp_rx#(
   parameter 
            BOARD_MAC = 48'h00_11_22_33_44_55,   
            BOARD_IP = {8'd192,8'd168,8'd1,8'd10}
)(
                    input               rst_n            ,   
                    input               clk              ,   // gmii读时钟
    (*IOB = "TRUE"*)input               gmii_rx_en_i     ,   // gmii读使能，增强时序稳定性
    (*IOB = "TRUE"*)input      [7 :0]   gmii_rx_data_i   ,   // gmii读数据
                    output reg [15:0]   udp_src_port_num ,
                    output reg          rx_done          ,
                    output reg [7:0]    udp_rx_data       
);
    
// reg define
reg [10:0]  cnt;                // 帧计时
reg [3:0]   cur_state, next_state;
reg         skip_en;            // 状态跳转信号
reg         error_en;           // 错误信号标志
reg [47:0]  eth_des_mac     ;   // 在以太网帧里面的目的mac缓存
reg [31:0]  ip_head_des_ip  ;   // destination ip in IP HEAD
reg [15:0]  eth_head_type   ;   // 在以太网帧里面的类型

reg         gmii_rx_en_reg;
reg [7:0]   gmii_rx_data_reg;
reg         rx_en_dly1;
reg         gmii_rx_en;
reg [7:0]   rx_data_dly1;
reg [7:0]   gmii_rx_data;
reg [15:0]  udp_port_num;
reg [31:0]  udp_rx_data_sum_r;
reg [7:0]   gmii_rx_data_r;
reg [10:0]  udp_data_cnt;

localparam 
            IDLE        = 4'd0,
            PRE         = 4'd1,
            SFD         = 4'd2,
            ETH_HEAD    = 4'd3,
            IP_HEAD     = 4'd4,
            UDP_HEAD    = 4'd5,
            DATA        = 4'd6,
            ERR         = 4'd8;
localparam
            UDP_PROTOCOL = 8'd17, // ip首部中udp协议的标号
            UDP_PORT_NUM1 = 16'd1234,
            UDP_PORT_NUM2 = 16'd1235;
/*****************************************************************
                            main code                           
*****************************************************************/

// 寄存输入信号并打两拍使用
always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        gmii_rx_en_reg <= 1'd0;
        gmii_rx_data_reg <= 8'd0;
    end
    else begin
        gmii_rx_en_reg <= gmii_rx_en_i;
        gmii_rx_data_reg <= gmii_rx_data_i;
    end
end

always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        rx_en_dly1   <= 1'd0;
        gmii_rx_en   <= 1'd0;
        rx_data_dly1 <= 8'd0;
        gmii_rx_data <= 8'd0;
    end
    else begin
        rx_en_dly1 <= gmii_rx_en_reg;
        gmii_rx_en <= rx_en_dly1;
        rx_data_dly1 <= gmii_rx_data_reg;
        gmii_rx_data <= rx_data_dly1;
    end
end

// 三段式状态地第一阶段：状态转移
always @(posedge clk or negedge rst_n) begin
    if(~rst_n)
        cur_state <= IDLE;
    else
        cur_state <= next_state;
end

// 第二阶段：状态转移判断
always @(*)begin
    next_state = IDLE;
    case (cur_state)
        IDLE: begin
            if(skip_en)
                next_state = PRE;
            else
                next_state = IDLE; 
        end  
        PRE: begin
            if(~error_en)
                if(skip_en)
                    next_state = SFD;
                else
                    next_state = PRE; 
            else
                next_state = ERR;
        end  
        SFD: begin
            if(~error_en)
                if(skip_en)
                    next_state = ETH_HEAD;
                else
                    next_state = SFD; 
            else
                next_state = ERR;
        end  
        ETH_HEAD: begin
            if(~error_en)
                if(skip_en)
                    next_state = IP_HEAD;
                else
                    next_state = ETH_HEAD; 
            else
                next_state = ERR;
        end  
        IP_HEAD: begin
            if(~error_en)
                if(skip_en)
                    next_state = UDP_HEAD;
                else
                    next_state = IP_HEAD; 
            else
                next_state = ERR;
        end    
        UDP_HEAD:begin
            if(~error_en)
                if(skip_en)
                    next_state = DATA;
                else
                    next_state = UDP_HEAD; 
            else
                next_state = ERR;
        end
        DATA: begin
            if(skip_en)
                next_state = IDLE;
            else
                next_state = DATA; 
        end  
        ERR:begin
            if(skip_en)
                next_state = IDLE;
            else
                next_state = ERR;
        end
    endcase
end

// 状态机第三段：状态输出
always @(posedge clk or negedge rst_n) begin
    if(~rst_n)begin
        cnt                     <= 11'd0;
        skip_en                 <= 1'd0;
        error_en                <= 1'd0;
        eth_des_mac             <= 48'd0;
        eth_head_type           <= 16'd0;
        udp_port_num            <= 16'd0;   
        udp_rx_data_sum_r       <= 32'd0;
        udp_src_port_num        <= 16'd0;
        gmii_rx_data_r          <= 8'd0;
        udp_data_cnt            <= 11'd0;
    end
    else begin
        skip_en <= 1'd0;
        rx_done <= 1'd0;
        case (next_state)   // 计时器放内部，逻辑更清晰，不依赖系统时钟的起始时间
            IDLE:begin      // 读取第一个55                
                if(gmii_rx_en && gmii_rx_data == 8'h55)begin
                    cnt <= 11'd0;
                    skip_en <= 1'd1;
                end
            end
            PRE: begin // 读剩下6个55
                if(gmii_rx_en)begin
                    cnt <= cnt +11'd1;
                    if(cnt < 11'd6 && gmii_rx_data == 8'h55)begin
                        if(cnt == 11'd5)begin
                            cnt <= 11'd0;
                            skip_en <= 1'd1;
                        end
                    end
                    else
                        error_en <= 1'd1;
                end
                else
                    error_en <= 1'd1;
            end
            SFD:begin// 帧起始界定符 d5
                if(gmii_rx_en)
                    if(gmii_rx_data == 8'hd5)
                        skip_en <= 1'd1;
                    else
                        error_en <= 1'd1;
                else
                    error_en <= 1'd1;
            end
            ETH_HEAD:begin  // 在帧开始时再复位要输出的信号
                if(gmii_rx_en)begin
                    udp_rx_data_sum_r <= 32'd0;
                    cnt <= cnt + 11'd1;
                    if(cnt < 11'd6)begin
                        eth_des_mac <= {eth_des_mac[39:0],gmii_rx_data};
                        if(cnt == 11'd5)
                            if({eth_des_mac[39:0],gmii_rx_data} != BOARD_MAC)// 判断是否是板子的mac地址
                                error_en <= 1'd1;            
                    end
                    if(cnt == 11'd12)
                        eth_head_type[15:8] <= gmii_rx_data;
                    if(cnt == 11'd13)
                        if({eth_head_type[15:8],gmii_rx_data} != 16'h0800)  // 判断是否为ip协议
                            error_en <= 1'd1;
                        else begin
                            cnt <= 11'd0;
                            skip_en <= 1'd1;
                        end
                end
                else
                    error_en <= 1'd1;
            end
            IP_HEAD:begin
                if(gmii_rx_en)begin
                    cnt <= cnt + 11'd1;
                    case (cnt) 
                        11'd9:if(gmii_rx_data != UDP_PROTOCOL)
                                error_en <= 1'd1;
                        11'd16:ip_head_des_ip[31:24] <= gmii_rx_data;
                        11'd17:ip_head_des_ip[23:16] <= gmii_rx_data;
                        11'd18:ip_head_des_ip[15: 8] <= gmii_rx_data;
                        11'd19:begin
                            if({ip_head_des_ip[31: 8],gmii_rx_data} != BOARD_IP) // 判断目的ip是不是板子
                                error_en <= 1'd1;
                            else begin
                                cnt <= 1'd0;
                                skip_en <= 1'd1;
                            end 
                        end
                    endcase
                end
                else
                    error_en <= 1'd1;
            end
            UDP_HEAD:begin
                if(gmii_rx_en)begin
                    cnt <= cnt + 11'd1;
                    case (cnt)
                        11'd0:udp_src_port_num[15:8] <= gmii_rx_data;
                        11'd1:udp_src_port_num[7:0] <= gmii_rx_data;
                        11'd2:udp_port_num[15:8] <= gmii_rx_data;
                        11'd3: if({udp_port_num[15:8],gmii_rx_data} == UDP_PORT_NUM1 || {udp_port_num[15:8],gmii_rx_data} == UDP_PORT_NUM2)
                                error_en <= 1'd1;
                        11'd7:begin
                            cnt <= 11'd0;
                            skip_en <= 1'd1;
                        end
                    endcase
                end
                else
                    error_en <= 1'd1;
            end
            DATA:begin // recived data just 1 or 0.
                if(gmii_rx_en)begin
                    skip_en <= 1'd1;
                    udp_rx_data <= gmii_rx_data;
                    rx_done <= 1'd1;
                end  
            end
            ERR: begin
                if(~gmii_rx_en)begin
                    cnt <= 11'd0;
                    skip_en <= 1'd1;
                    error_en <= 1'd0;
                end
            end
        endcase
    end
end

endmodule